package iblink.adapter;

import iblink.core.HistoricalData;
import iblink.core.ScannerData;
import iblink.event.ContractDetailsResponseEvent;
import iblink.event.HistoricalDataResponseEvent;
import iblink.event.IBErrorEvent;
import iblink.event.ScannerDataResponseEvent;
import iblink.event.ScannerParametersResponseEvent;

import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import packutil.DateUtil;
import packutil.event.AEventGenerator;

import com.ib.client.Contract;
import com.ib.client.ContractDetails;
import com.ib.client.EWrapper;
import com.ib.client.Execution;
import com.ib.client.Order;
import com.ib.client.OrderState;
import com.ib.client.TickType;
import com.ib.client.UnderComp;

public class IBWrapper extends AEventGenerator implements EWrapper {

	private Log log = LogFactory.getLog(IBWrapper.class);

	private Map<Integer, List<?>> responseDataStaging = new Hashtable<Integer, List<?>>();

	private IBClient ibClient;

	protected void setClient(IBClient ibClient) {
		this.ibClient = ibClient;
	}

	@Override
	public void error(Exception e) {
		log.error("000 public void error(Exception e)", e);
	}

	@Override
	public void error(String str) {
		log.error("001 " + str);
	}

	public void logNotReallyAnError(int id, int errorCode, String errorMsg) {
		log.info(getClass().getSimpleName() + ".logNotReallyAnError: id[" + id
				+ "] errorCode[" + errorCode + "] errorMsg [" + errorMsg + "]");
	}

	@Override
	public void error(int id, int errorCode, String errorMsg) {

		// System.out.println("error(" + id + "\t" + errorCode + "\t" + errorMsg
		// + ")");

		switch (errorCode) {
		case (162):
			if (errorMsg.contains("API scanner subscription cancelled")) {
				logNotReallyAnError(id, errorCode, errorMsg);
				return;
			} else if (errorMsg.contains("HMDS query returned no data")) {
				logNotReallyAnError(id, errorCode, errorMsg);
				// return an empty historical data list and let the caller deal
				// with it.. not really an error
				throwEvent(new HistoricalDataResponseEvent(id,
						new ArrayList<HistoricalData>()));
				return;
			}
			break;
		case (165):
			if (errorMsg.contains("HMDS server connection was successful")) {
				logNotReallyAnError(id, errorCode, errorMsg);
				return;
			}
			break;
		case (502):
			if (errorMsg.contains("Couldn't connect to TWS")) {
				ibClient.couldNotConnectToTWS();
				return;
			}
		case (1100):
			ibClient.ibConnectivityLost();
			return;
		case (1101):
			ibClient.ibConnectivityRestoredDataLost();
			return;
		case (1102):
			ibClient.ibConnectivityRestoredDataMaintained();
			return;
		case (1300):
			ibClient.twsConnectionSocketDropped();
			return;
		case (2105):
			ibClient.historicalDataFarmDisconnected();
			return;
		case (2106):
			ibClient.historicalDataFarmConnected();
			return;
		case (2110):
			ibClient.ibConnectivityBroken();
			return;
		default:
			log.error("002\t" + id + "\t" + errorCode + "\t" + errorMsg);
		}

		throwEvent(new IBErrorEvent(id, errorCode, errorMsg));
	}

	@Override
	public void connectionClosed() {
		log.info("connectionClosed()");
	}

	@Override
	public void tickPrice(int tickerId, int field, double price,
			int canAutoExecute) {
		System.out.println("tickPrice\t" + field + "\t" + price);
	}

	@Override
	public void tickSize(int tickerId, int field, int size) {
		System.out.println("tickSize" + "\t" + tickerId + "\t" + field + "\t"
				+ size);
	}

	@Override
	public void tickOptionComputation(int tickerId, int field,
			double impliedVol, double delta, double optPrice,
			double pvDividend, double gamma, double vega, double theta,
			double undPrice) {
		System.out.println("tickOptionComputation()");
	}

	@Override
	public void tickGeneric(int tickerId, int tickType, double value) {
		System.out.println("generic: " + tickerId + "\t" + tickType + "\t"
				+ value);
	}

	@Override
	public void tickString(int tickerId, int tickType, String value) {
		System.out.println("tickString(tickerId[" + tickerId + "] tickType["
				+ tickType + "] TickType[" + TickType.getField(tickType)
				+ "] value[" + value + "])");
	}

	@Override
	public void tickEFP(int tickerId, int tickType, double basisPoints,
			String formattedBasisPoints, double impliedFuture, int holdDays,
			String futureExpiry, double dividendImpact, double dividendsToExpiry) {
		System.out.println("tickEFP()");
	}

	@Override
	public void orderStatus(int orderId, String status, int filled,
			int remaining, double avgFillPrice, int permId, int parentId,
			double lastFillPrice, int clientId, String whyHeld) {
		System.out.println("orderStatus()");
	}

	@Override
	public void openOrder(int orderId, Contract contract, Order order,
			OrderState orderState) {
		System.out.println("openOrder()");
	}

	@Override
	public void openOrderEnd() {
		System.out.println("openOrderEnd()");
	}

	@Override
	public void updateAccountValue(String key, String value, String currency,
			String accountName) {
		System.out.println("updateAccountValue()");
	}

	@Override
	public void updatePortfolio(Contract contract, int position,
			double marketPrice, double marketValue, double averageCost,
			double unrealizedPNL, double realizedPNL, String accountName) {
		System.out.println("updatePortfolio()");
	}

	@Override
	public void updateAccountTime(String timeStamp) {
		System.out.println("updateAccountTime()");
	}

	@Override
	public void accountDownloadEnd(String accountName) {
		System.out.println("accountDownloadEnd()");
	}

	@Override
	public void nextValidId(int orderId) {
		System.out.println("nextValidId()");
	}

	@SuppressWarnings("unchecked")
	@Override
	public void contractDetails(int reqId, ContractDetails contractDetails) {

		if (!responseDataStaging.containsKey(reqId)) {
			responseDataStaging.put(reqId, new ArrayList<ContractDetails>());
		}

		((List<ContractDetails>) responseDataStaging.get(reqId))
				.add(contractDetails);
	}

	@Override
	public void bondContractDetails(int reqId, ContractDetails contractDetails) {
		System.out.println("bondContractDetails()");
	}

	@SuppressWarnings("unchecked")
	@Override
	public void contractDetailsEnd(int reqId) {
		throwEvent(new ContractDetailsResponseEvent(reqId,
				(List<ContractDetails>) responseDataStaging.remove(reqId)));
	}

	@Override
	public void execDetails(int reqId, Contract contract, Execution execution) {
		System.out.println("execDetails()");
	}

	@Override
	public void execDetailsEnd(int reqId) {
		System.out.println("execDetailsEnd()");
	}

	@Override
	public void updateMktDepth(int tickerId, int position, int operation,
			int side, double price, int size) {
		System.out.println("updateMktDepth()");
	}

	@Override
	public void updateMktDepthL2(int tickerId, int position,
			String marketMaker, int operation, int side, double price, int size) {
		System.out.println("updateMktDepthL2()");
	}

	@Override
	public void updateNewsBulletin(int msgId, int msgType, String message,
			String origExchange) {
		System.out.println("updateNewsBulletin()");
	}

	@Override
	public void managedAccounts(String accountsList) {
		System.out.println("managedAccounts()");
	}

	@Override
	public void receiveFA(int faDataType, String xml) {
		System.out.println("receiveFA()");
	}

	@SuppressWarnings("unchecked")
	@Override
	public void historicalData(int reqId, String date, double open,
			double high, double low, double close, int volume, int count,
			double wap, boolean hasGaps) {

		// System.out.println("historicalData() " + date);

		if (date.contains("finished")) {
			throwEvent(new HistoricalDataResponseEvent(reqId,
					(List<HistoricalData>) responseDataStaging.remove(reqId)));
			return;
		}

		if (!responseDataStaging.containsKey(reqId)) {
			responseDataStaging.put(reqId, new ArrayList<HistoricalData>());
		}

		// TODO: multiple volume by 1,000?
		
		Date d = DateUtil.parseDate(date, "yyyyMMdd HH:mm:ss");
		((List<HistoricalData>) responseDataStaging.get(reqId))
				.add(new HistoricalData(d, open, high, low, close, volume,
						count, wap, hasGaps));
	}

	@Override
	public void scannerParameters(String xml) {
		System.out.println("scannerParms");
		throwEvent(new ScannerParametersResponseEvent(-1, xml));
	}

	@SuppressWarnings("unchecked")
	@Override
	public void scannerData(int reqId, int rank,
			ContractDetails contractDetails, String distance, String benchmark,
			String projection, String legsStr) {

		System.out.println("scannerData");

		if (!responseDataStaging.containsKey(reqId)) {
			responseDataStaging.put(reqId, new ArrayList<ScannerData>());
		}

		ScannerData sd = new ScannerData(rank, contractDetails, distance,
				benchmark, projection, legsStr);
		System.out.println(sd);
		((List<ScannerData>) responseDataStaging.get(reqId)).add(sd);

		// System.out.println("scannerData(" + reqId + "\t" + rank + "\t"
		// + contractDetails + "\t" + distance + "" + benchmark + "\t"
		// + projection + "\t" + legsStr + "\t"
		// + contractDetails.m_summary.m_localSymbol + ")");
	}

	@SuppressWarnings("unchecked")
	@Override
	public void scannerDataEnd(int reqId) {

		System.out.println("scannerDataEnd");
		throwEvent(new ScannerDataResponseEvent(reqId,
				(List<ScannerData>) responseDataStaging.remove(reqId)));
	}

	@Override
	public void realtimeBar(int reqId, long time, double open, double high,
			double low, double close, long volume, double wap, int count) {
		System.out.println("realtimeBar()");
	}

	@Override
	public void currentTime(long time) {
		System.out.println("currentTime()");
	}

	@Override
	public void fundamentalData(int reqId, String data) {
		System.out.println("fundamentalData()");
	}

	@Override
	public void deltaNeutralValidation(int reqId, UnderComp underComp) {
		System.out.println("deltaNeutralValidation()");
	}

	@Override
	public void tickSnapshotEnd(int reqId) {
		System.out.println("tickSnapshotEnd(" + reqId + ")");
	}
}
